/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-基础组件封装层-SpringBoot组件封装-缺省实现
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.boot.web.filters;

import com.taipingframework.boot.web.client.WebHelper;
import com.taipingframework.boot.web.support.RepeatableRequestWrapper;
import com.taipingframework.boot.web.support.WebMvcHelper;
import com.taipingframework.utility.constant.ApplicationConstant;
import com.taipingframework.utility.thread.threadlocal.TransmittableThreadLocalHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingResponseWrapper;
import springfox.documentation.swagger2.web.Swagger2Controller;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

/**
 * OncePerRequestFilter 是spring提供的过滤器
 */
@Slf4j
public class DefaultSpringFilter extends OncePerRequestFilter {

    private String excludeUrlPatterns;

    @Override
    protected void initFilterBean() throws ServletException {
        String[] stackTraceArray = Arrays.stream(Thread.currentThread().getStackTrace())
                .map(StackTraceElement::toString)
                .toArray(String[]::new);
        log.debug("DefaultSpringFilter.init\r\n\r\tat {}", String.join("\r\n\r\tat ", stackTraceArray[1]));

        FilterConfig filterConfig = super.getFilterConfig();
        if (filterConfig != null) {
            excludeUrlPatterns = filterConfig.getInitParameter("excludeUrlPatterns");
        }
    }

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain chain) throws ServletException, IOException {
        log.debug("DefaultSpringFilter.doFilterInternal - start");

        //* 记录请求开始时间戳(异步请求场景，此时肯定还没有开始直接Controller层的业务编码)
        TransmittableThreadLocalHelper.getEnvironment().putIfAbsent(ApplicationConstant.START_REQUEST_TIME_KEY, String.valueOf(System.currentTimeMillis()));

        //* 记录发送HTTP请求的远程客户端设备标识
        String remoteClientId = WebHelper.getIpAddr(request);
        TransmittableThreadLocalHelper.getEnvironment().putIfAbsent(ApplicationConstant.REMOTE_CLIENT_IP_KEY, remoteClientId);

        //* 初始化当前HTTP请求线程缓存
        WebMvcHelper.initialThreadCache(request, response);

        // 自定义Servlet目前的实现如果使用`RepeatableRequestWrapper`和`ContentCachingResponseWrapper`两个包装类，是存在问题的。
        // 此处通过一个简单的url判断特殊处理这个场景！
        if (request.getRequestURI().startsWith(excludeUrlPatterns)) {
            chain.doFilter(request, response);
        } else {
            RepeatableRequestWrapper requestWrapper = request instanceof RepeatableRequestWrapper
                    ? (RepeatableRequestWrapper) request
                    : new RepeatableRequestWrapper(request);

            ContentCachingResponseWrapper responseWrapper = response instanceof ContentCachingResponseWrapper
                    ? (ContentCachingResponseWrapper) response
                    : new ContentCachingResponseWrapper(response);

            chain.doFilter(requestWrapper, responseWrapper);
            //* 此处的做法比较保守 - 别人复用你放在缓冲区的数据，但别人并不知道应该在什么时候将缓冲区的数据写入到IO，这件事需要被确保一定不能遗忘。
            //* 可以理解为将缓冲区的数据写入到ServletResponse对象中。
            //* 但是，如果是异步请求，由于过滤器会提前退出，所以下面这句代码将不会生效！
            /*# 为了解决异步请求场景下全局异常无法反馈给客户端的这个问题，需要将下面这句代码放在拦截器中
            responseWrapper.copyBodyToResponse();*/

            /*因为我们自定义的拦截器和AOP切面都无法拦截swagger2自定义的Swagger2Controller，所以此处需要特殊处理!!!
            @Bean
            public HandlerMapping swagger2ControllerMapping(
                Environment environment,
                DocumentationCache documentationCache,
                ServiceModelToSwagger2Mapper mapper,
                JsonSerializer jsonSerializer) {
                    return new PropertySourcedRequestMappingHandlerMapping(
                        environment,
                        new Swagger2Controller(environment, documentationCache, mapper, jsonSerializer));
            }*/
            if (request.getRequestURI().startsWith(ApplicationConstant.ERROR_PATH)
                    || request.getRequestURI().startsWith(Swagger2Controller.DEFAULT_URL)) {
                log.debug("DefaultSpringFilter.doFilterInternal - 将缓冲区数据写入输出流");
                responseWrapper.copyBodyToResponse();
            }

            log.debug("DefaultSpringFilter.doFilterInternal - end");

        }
    }

}
